import json
import os
import random
import time
from io import BytesIO
import cv2
import numpy as np
from Demos.win32ts_logoff_disconnected import username
from PIL import Image, ImageDraw, ImageFont
import execjs
import re
from bs4 import BeautifulSoup
import ddddocr
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
import base64


acc = "18320975328"
pwd = "Aafbike2985"
# 固定公钥Base64字符串
_PUBLIC_KEY_B64 = (
    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBgxenWGQrynpHxvRsnlXWBFCr"
    "Ghf3eES3/aajLV+oceh1m4xZyUSA5mMoRvdvfmo+snVPuGPTwzz4MP1xLSgEtcQRz"
    "l1atza0Kt106HBKihKqhqJsLTSRE0xiGcZJMPpcpho/xLI+T3nmsHwQTMQD+TAgmz"
    "LBnffs6Hoart6FPQIDAQAB"
)


def rsa_encrypt(message: str) -> str:
    """
    使用写死的RSA公钥加密明文，并返回Base64密文字符串。
    """
    # 1. DER解码+PEM格式组装
    der_data = base64.b64decode(_PUBLIC_KEY_B64)
    pem = b"-----BEGIN PUBLIC KEY-----\n" + \
          base64.encodebytes(der_data).replace(b'\n\n', b'\n') + \
          b"-----END PUBLIC KEY-----\n"
    # 2. 加载公钥
    pubkey_obj = serialization.load_pem_public_key(pem)
    # 3. 加密
    ciphertext = pubkey_obj.encrypt(
        message.encode('utf-8'),
        padding.PKCS1v15()
    )
    # 4. Base64编码密文
    return base64.b64encode(ciphertext).decode("utf-8")


with open("demo.js", "r", encoding="utf-8") as f:
    js = f.read()
js_code = execjs.compile(js)

proxies = {
    'http': 'socks5://127.0.0.1:8442',
    'https': 'socks5://127.0.0.1:8442'
}


def guess_captcha_type(files, captcha_info):
    """根据图片文件和提示字段信息自动推断验证码类型"""
    bg_exists = "bg" in files and files["bg"]
    slice_exists = "slice" in files and files["slice"]
    fullbg_exists = "fullbg" in files and files["fullbg"]
    info = (captcha_info.get("info", "") if captcha_info else "") + (
        captcha_info.get("words", "") if captcha_info else "")

    # 优先判断“升级”字眼特判为init
    if "升级" in info:
        return "init"
    # 三个文件都存在，是滑块
    if bg_exists and slice_exists and fullbg_exists:
        return "slide"
    # 只有fullbg，是文字验证码
    if fullbg_exists and not bg_exists and not slice_exists:
        return "word"
    # 只有fullbg和slice，没有bg，是点选
    if fullbg_exists and slice_exists and not bg_exists:
        return "click"
    # 其它情况未知
    return "unknown"


def parse_and_save_captcha_images(response_text, save_dir='captcha_imgs', return_ctxid=True):
    """
    解析返回的回调字符串，抓取bg, slice, fullbg文件，根据static_servers和https拼接url，并下载保存到本地。
    同时返回captcha_info（含type/info等字段）
    """
    m = re.search(r'cb\((\{.*?\})\);?$', response_text)
    if not m:
        print("回调内容提取失败")
        if return_ctxid:
            return (None, None, None)
        else:
            return None

    result_json = json.loads(m.group(1))
    data = result_json.get("Data", {})
    captcha_info_str = data.get("CaptchaInfo")
    if not captcha_info_str:
        print("CaptchaInfo为空")
        if return_ctxid:
            return (None, None, None)
        else:
            return None

    captcha_info = json.loads(captcha_info_str)
    new_ctxid = captcha_info.get("captchaContextId", None)
    bg = captcha_info.get("bg")
    slice_img = captcha_info.get("slice")
    fullbg = captcha_info.get("fullbg")
    static_servers = captcha_info.get("static_servers", [])
    https_on = captcha_info.get("https", False)

    if not static_servers:
        print("static_servers为空")
        if return_ctxid:
            return (None, None, None)
        else:
            return None

    static_server = static_servers[0]
    if not static_server.startswith("http"):
        static_server = ("https://" if https_on else "http://") + static_server.lstrip("/")

    def make_url(fname):
        if fname:
            fname = fname.lstrip("/")
            return static_server.rstrip("/") + "/" + fname
        return None

    os.makedirs(save_dir, exist_ok=True)
    img_infos = [
        ("bg", bg),
        ("slice", slice_img),
        ("fullbg", fullbg),
    ]

    saved_files = {}
    for label, fname in img_infos:
        url = make_url(fname)
        if url and fname:
            try:
                print("下载", label, url)
                img_resp = requests.get(url, proxies=proxies)
                if img_resp.status_code == 200:
                    ext = os.path.splitext(fname)[1] or ".jpg"
                    save_path = os.path.join(save_dir, f"{label}{ext}")
                    with open(save_path, "wb") as f:
                        f.write(img_resp.content)
                    print(f"{label} 已保存到 {save_path}")
                    saved_files[label] = save_path
                else:
                    print(f"{label} 下载失败: {url} 状态码={img_resp.status_code}")
            except Exception as e:
                print(f"{label} 下载异常: {e}")
        else:
            print(f"{label} 文件名不存在，跳过。")

    if return_ctxid:
        return saved_files, new_ctxid, captcha_info
    else:
        return


def login4():
    url = "https://exaccount2.eastmoney.com/home/Login4"
    headers = {
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Origin": "https://exaccount2.eastmoney.com",
        "Pragma": "no-cache",
        "Referer": "https://exaccount2.eastmoney.com/home/Login4?rc=943878679",
        "RequestVerificationToken": "U_KfbsQxFGDu0MP6bVwvFWznSjgwhcFBTulAb2aJ2vjHLiTKjJn2J1hu7B8OlOVc9nwcaUuAq2VxHs-RsrJpijcmWwY1",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
        "X-Requested-With": "XMLHttpRequest",
        "deviceType": "Web",
        "domainName": "passport2.eastmoney.com",
        "productType": "UserPassport",
        "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }
    cookies = {
        "qgqp_b_id": "af4a3d01781b5555726d9a17e425be7a",
        "st_nvi": "2xfzbOLOA9PSvGMSOq0F6856a",
        "nid": "04d51e4d408a5c3c863d17873895a224",
        "nid_create_time": "1754271580335",
        "gvi": "a5R5YyyjtEfOf8i6-mdDl6c36",
        "gvi_create_time": "1754271580335",
        "p_origin": "https%3A%2F%2Fpassport2.eastmoney.com",
        "st_si": "35000435510543",
        "st_pvi": "67053581251557",
        "st_sp": "2025-08-04%2009%3A39%3A40",
        "st_inirUrl": "https%3A%2F%2Fpassport2.eastmoney.com%2F",
        "st_sn": "1",
        "st_psi": "2025080510450375-0-0496650987",
        "st_asi": "delete"
    }
    params = {
        "rc": "354855057"
    }
    response = requests.get(url, headers=headers, params=params,proxies=proxies,cookies=cookies)
    html = response.text
    capt_app_id = None
    match = re.search(r"var\s+CaptAppId\s*=\s*'(\d+)';", html)
    if match:
        capt_app_id = match.group(1)
        print("解析到的 CaptAppId:", capt_app_id)
    else:
        print("CaptAppId 没找到")
    soup = BeautifulSoup(html, 'html.parser')
    ctxid = soup.find('input', id='hdAccountCaptContextId')
    if ctxid is None:
        print("ctxid not found")
        return
    print("Login4 status==>", response.status_code)
    print("Login4 ctxid['value']==>", ctxid['value'])
    cid = ctxid['value']
    return cid, capt_app_id


def login3(cid, val):
    headers = {
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Origin": "https://exaccount2.eastmoney.com",
        "Pragma": "no-cache",
        "Referer": "https://exaccount2.eastmoney.com/home/Login4?rc=943878679",
        "RequestVerificationToken": "U_KfbsQxFGDu0MP6bVwvFWznSjgwhcFBTulAb2aJ2vjHLiTKjJn2J1hu7B8OlOVc9nwcaUuAq2VxHs-RsrJpijcmWwY1",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
        "X-Requested-With": "XMLHttpRequest",
        "deviceType": "Web",
        "domainName": "passport2.eastmoney.com",
        "productType": "UserPassport",
        "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }
    url = "https://exaccount2.eastmoney.com/JsonAPI/Login3"
    data = {
        "username": acc,
        "password": rsa_encrypt(pwd),
        "captconetxt": cid,
        "captvalidate": val
    }
    response = requests.post(url, headers=headers, data=data, proxies=proxies)

    print(response.text)
    print(response.status_code)


def val_validate(type, appid, ctxid, dist, track_str, duration):
    """
    type: "slide"
    appid, ctxid: 登录流程里的
    dist: 滑块识别距离
    track_str: generate_track(dist)生成的字符串
    duration: 轨迹最后一个点的时间（ms）
    """
    # 用 execjs 动态调用JS
    req = js_code.call("validate", acc, pwd, type, appid, ctxid, dist, track_str, duration)
    print("req ==>", req)
    headers = {
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Origin": "https://exaccount2.eastmoney.com",
        "Pragma": "no-cache",
        "Referer": "https://exaccount2.eastmoney.com/home/Login4?rc=943878679",
        "RequestVerificationToken": "U_KfbsQxFGDu0MP6bVwvFWznSjgwhcFBTulAb2aJ2vjHLiTKjJn2J1hu7B8OlOVc9nwcaUuAq2VxHs-RsrJpijcmWwY1",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
        "X-Requested-With": "XMLHttpRequest",
        "deviceType": "Web",
        "domainName": "passport2.eastmoney.com",
        "productType": "UserPassport",
        "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }
    cookies = {
        "qgqp_b_id": "af4a3d01781b5555726d9a17e425be7a",
        "st_nvi": "2xfzbOLOA9PSvGMSOq0F6856a",
        "nid": "04d51e4d408a5c3c863d17873895a224",
        "nid_create_time": "1754271580335",
        "gvi": "a5R5YyyjtEfOf8i6-mdDl6c36",
        "gvi_create_time": "1754271580335",
        "st_si": "40220433986802",
        "st_asi": "delete",
        "p_origin": "https%3A%2F%2Fpassport2.eastmoney.com",
        "st_pvi": "67053581251557",
        "st_sp": "2025-08-04%2009%3A39%3A40",
        "st_inirUrl": "https%3A%2F%2Fpassport2.eastmoney.com%2F",
        "st_sn": "2",
        "st_psi": "2025080816010439-0-5342796557"
    }
    url = "https://smartvcode2.eastmoney.com/Titan/api/captcha/Validate"

    params = {
        "callback": "cb",
        "ctxid": ctxid,
        "request": req,
        "_": str(int(time.time() * 1000))
    }
    response = requests.get(url, headers=headers, cookies=cookies, params=params,proxies=proxies)
    print(response.text)
    return appid, ctxid, response.text


def get_captcha(cid, appid):
    req = js_code.call("get_cap", acc, pwd, appid, cid)
    print(req)
    headers = {
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Origin": "https://exaccount2.eastmoney.com",
        "Pragma": "no-cache",
        "Referer": "https://exaccount2.eastmoney.com/home/Login4?rc=943878679",
        "RequestVerificationToken": "U_KfbsQxFGDu0MP6bVwvFWznSjgwhcFBTulAb2aJ2vjHLiTKjJn2J1hu7B8OlOVc9nwcaUuAq2VxHs-RsrJpijcmWwY1",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
        "X-Requested-With": "XMLHttpRequest",
        "deviceType": "Web",
        "domainName": "passport2.eastmoney.com",
        "productType": "UserPassport",
        "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }
    cookies = {
        "qgqp_b_id": "af4a3d01781b5555726d9a17e425be7a",
        "st_nvi": "2xfzbOLOA9PSvGMSOq0F6856a",
        "nid": "04d51e4d408a5c3c863d17873895a224",
        "nid_create_time": "1754271580335",
        "gvi": "a5R5YyyjtEfOf8i6-mdDl6c36",
        "gvi_create_time": "1754271580335",
        "st_si": "66753585846447",
        "st_asi": "delete",
        "p_origin": "https%3A%2F%2Fpassport2.eastmoney.com",
        "st_pvi": "67053581251557",
        "st_sp": "2025-08-04%2009%3A39%3A40",
        "st_inirUrl": "https%3A%2F%2Fpassport2.eastmoney.com%2F",
        "st_sn": "2",
        "st_psi": "20250806190310262-0-4203942989"
    }
    url = "https://smartvcode2.eastmoney.com/Titan/api/captcha/get"
    params = {
        "callback": "cb",
        "ctxid": cid,
        "request": req,
        "_": str(int(time.time() * 1000))
    }
    response = requests.get(url, headers=headers, params=params,proxies=proxies,cookies=cookies)

    print(response.text)
    print(response)
    return response.text


def get_shuffle_indices():
    e = [int(x) for x in "6_11_7_10_4_12_3_1_0_5_2_9_8".split('_')]
    t = []
    for r in range(52):
        n = 2 * e[int((r % 26) / 2)] + (r % 2)
        if (int(r / 2) % 2) != 0:
            n += -1 if (r % 2) else 1
        if r < 26:
            n += 26
        t.append(n)
    return t


def shuffle_slices(original_img, img_name):
    original_img = Image.open(BytesIO(original_img))
    # 切片大小
    slice_width = 12
    slice_height = 80

    # 顺序还原映射（JS a() 逻辑复现）
    def build_index():
        order = "6_11_7_10_4_12_3_1_0_5_2_9_8".split("_")
        order = list(map(int, order))
        result = []
        for r in range(52):
            idx = 2 * order[int((r % 26) / 2)] + (r % 2)
            if (int(r / 2) % 2) == 0:
                idx += -1 if (r % 2) else 1
            if r >= 26:
                idx += 26
            result.append(idx)
        return result

    mapping = build_index()

    # 创建新图像用于拼接（宽度 312，高度 160）
    canvas = Image.new("RGB", (26 * slice_width, 2 * slice_height))

    for i in range(52):
        src_index = mapping[i]
        row = 0 if i < 26 else 1
        src_row = 0 if src_index < 26 else 1
        src_col = src_index % 26

        # 切片区域
        left = src_col * slice_width
        upper = src_row * slice_height
        right = left + slice_width
        lower = upper + slice_height

        # 粘贴区域
        dst_left = (i % 26) * slice_width
        dst_upper = row * slice_height

        # 裁剪并粘贴
        part = original_img.crop((left, upper, right, lower))
        canvas.paste(part, (dst_left, dst_upper))

    # 保存还原后的图
    canvas.save(f"{img_name}.jpg")


def generate_real_track(target_x, base_y=0, min_time=900, max_time=1700):
    track = []
    cur_x = 0
    cur_y = base_y
    cur_t = 0

    total_t = random.randint(min_time, max_time)
    steps = random.randint(60, 90)
    remain_dist = target_x

    track.append([0, cur_y, 0])  # 首点: “准备起拖”

    for i in range(1, steps):
        if remain_dist <= 0:
            break
        # 微步分布
        if i < steps * 0.24:  # 起步
            dx = random.choice([1, 1, 2, 1, 0])
        elif i > steps * 0.8:  # 末尾
            dx = random.choice([0, 1, 0, -1, 1])
        else:  # 匀速中段
            dx = random.choice([1, 2, 1, 1, 2, 1, 0, -1])

        if cur_x + dx > target_x:
            dx = target_x - cur_x
        if cur_x + dx < 0:
            dx = 0
        cur_x += dx
        remain_dist = max(0, target_x - cur_x)

        # y微调
        if i % 12 == 0:
            cur_y += random.choice([-1, 1, 0, 0])
            cur_y = max(-10, min(10, cur_y))
        # 时间不均匀增加
        if i < 5:
            dt = random.randint(60, 120)
        elif i > steps - 7:
            dt = random.randint(80, 200)
        else:
            dt = random.randint(12, 36)
        cur_t += dt

        track.append([cur_x, cur_y, cur_t])

    # 补齐终点
    if cur_x < target_x:
        cur_t += random.randint(40, 120)
        cur_x = target_x
        track.append([cur_x, cur_y, cur_t])

    # 尾端停顿
    for _ in range(random.randint(3, 5)):
        cur_t += random.randint(120, 390)
        track.append([cur_x, cur_y + random.choice([-1, 0, 1]), cur_t])

    # 去重 x/y
    output_track = []
    last = None
    for pt in track:
        if last is None or pt[:2] != last:
            output_track.append(pt)
            last = pt[:2]

    # 格式化
    track_str = ':'.join("{},{},{}".format(x, y, t) for x, y, t in output_track)
    duration = output_track[-1][2]
    return track_str, duration


def demo_par_res(response_text):
    # 1. 提取大括号内容为json
    m = re.search(r'cb\((\{.*?\})\);?$', response_text)
    result_json = json.loads(m.group(1))
    return result_json


def generate_u_from_coords(coords, start=0, interval=700):
    u_list = []
    t = start
    for i, item in enumerate(coords):
        u_list.append(f"{item['x']},{item['y']},{t}")
        # interval可以微随机
        t += interval + random.randint(-80, 60)
    return ':'.join(u_list), t + random.randint(200, 360)
class DdddocrWrapper:
    def __init__(self, font_type="./msyhl.ttc", font_size=20):
        self.ocr = ddddocr.DdddOcr(show_ad=False)
        self.xy_ocr = ddddocr.DdddOcr(det=True, show_ad=False)
        self.font_type = font_type
        self.font_size = font_size

    def draw_img(self, content, xy_list):
        font = ImageFont.truetype(self.font_type, self.font_size)
        img = Image.open(BytesIO(content))
        draw = ImageDraw.Draw(img)
        words = []
        for row in xy_list:
            x1, y1, x2, y2 = row
            draw.line(([(x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1)]), width=1, fill="red")
            corp = img.crop(row)
            img_byte = BytesIO()
            corp.save(img_byte, 'png')
            word = self.ocr.classification(img_byte.getvalue())
            words.append(word)
            y = y1 - 30 if y2 > 300 else y2
            draw.text((int((x1 + x2) // 2), y), word, font=font, fill="red")
        # img.show()   # 可视化用
        return words

    def ddddocr_click_identify(self, content, crop_size=None):
        img = Image.open(BytesIO(content))
        if crop_size:
            img = img.crop(crop_size)
            img_byte = BytesIO()
            img.save(img_byte, 'png')
            content = img_byte.getvalue()
        xy_list = self.xy_ocr.detection(content)
        words = self.draw_img(content, xy_list)
        return dict(zip(words, xy_list))

    def get_word_centers(self, content):
        """返回字与中心坐标的对应dict"""
        result = self.ddddocr_click_identify(content)
        center_dict = {}
        for key, xy in result.items():
            center_dict[key] = (int((xy[0] + xy[2]) / 2), int((xy[1] + xy[3]) / 2))
        return center_dict


def get_captcha_click_xy(img_bytes: bytes, target_wordlist, font_type="./msyhl.ttc"):
    """
    img_bytes: bytes 验证码图片内容
    target_wordlist: List[str] 需点选的文字列表
    返回：[{x:..., y:...}, ...]，按照target_wordlist顺序
    """
    ddd = DdddocrWrapper(font_type=font_type)
    word2xy = ddd.get_word_centers(img_bytes)

    # 打印识别到的所有字符和坐标，方便调试
    print("OCR识别结果:", word2xy)

    yzm_xy = []
    for word in target_wordlist:
        xy = word2xy.get(word)
        if not xy:
            print(f"OCR无法识别'{word}'，需手动输入")
            xy = eval(input(f"请人工输入'{word}'的坐标，如(123,45): "))
        else:
            print(f"OCR成功识别'{word}'，坐标为{xy}")
        yzm_xy.append({"x": xy[0], "y": xy[1]})

    # 检查是否有重复坐标
    coords_set = set((item['x'], item['y']) for item in yzm_xy)
    if len(coords_set) != len(yzm_xy):
        print("警告：检测到重复坐标！请检查输入。")

    return yzm_xy

# ------- 主流程示例 -------
if __name__ == '__main__':
    cid, appid = login4()
    get_captcha(cid, appid)
    res = get_captcha(cid, appid)

    files, new_cid, captcha_info = parse_and_save_captcha_images(res, return_ctxid=True)
    if not files:
        print("验证码图片解析失败，流程中断")
        exit(1)

    captcha_type = guess_captcha_type(files, captcha_info)
    print('自动判定类型:', captcha_type)
    os.makedirs('captcha_imgs/restore', exist_ok=True)

    if captcha_type == "slide":
        # 滑块验证码
        shuffle_slices(open(files["bg"], "rb").read(), "captcha_imgs/restore/bg")
        shuffle_slices(open(files["fullbg"], "rb").read(), "captcha_imgs/restore/fullbg")
        distance = int(input("请输入滑动距离："))
        track_str, duration = generate_real_track(distance)
        print(f"distance={distance}, track_str={track_str}, duration={duration}")
        print("\n开始请求校验接口...")
        appid, ctxid, result = val_validate(
            type="slide",
            appid=appid,
            ctxid=cid,
            dist=distance,
            track_str=track_str,
            duration=duration,
        )
    elif captcha_type == "click":
        # 关键：先还原fullbg到restore文件夹
        shuffle_slices(open(files["fullbg"], "rb").read(), "captcha_imgs/restore/fullbg")

        restore_bg_path = os.path.join("captcha_imgs/restore/fullbg.jpg")
        with open(restore_bg_path, "rb") as f:
            print("点选验证码内容：", captcha_info.get("info") or captcha_info.get("words"))
            wordstr = captcha_info.get("info") or captcha_info.get("words")
            wordlist = [w for w in wordstr.replace("，", ",").split(",") if w.strip()]
            yzm_xy = get_captcha_click_xy(f.read(), wordlist)
        print("提取的坐标:", yzm_xy)

        points, duration = generate_u_from_coords(yzm_xy, start=0, interval=600)
        print("自动生成points:", points)
        print("自动生成duration:", duration)

        print("\n开始请求校验接口...")
        appid, ctxid, result = val_validate(
            type="click",
            appid=appid,
            ctxid=cid,
            dist=points,
            track_str="",
            duration=duration
        )
    elif captcha_type == "word":
        # 文字验证码
        shuffle_slices(open(files["fullbg"], "rb").read(), "captcha_imgs/restore/fullbg")
        print("文字验证码内容：", captcha_info.get("info") or captcha_info.get("words"))
        yzm = input("请输入验证码：")
        print("\n开始请求校验接口...")
        appid, ctxid, result = val_validate(
            type="word",
            appid=appid,
            ctxid=cid,
            dist=yzm,
            track_str="",
            duration=0,
        )

    elif captcha_type == "init":
        print("遇到升级提示（init），流程中断，需要人工处理升级。")
        exit(1)

    else:
        print(f"无效或不支持的验证码类型({captcha_type})，流程中断")
        exit(1)

    print("校验返回结果为:")
    print(result)
    ress = demo_par_res(result)
    print("ress===>", ress)
